/*
 * daemon.c
 *
 *  Created on: 2014年7月30日
 *      Author: keengo
 */
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <sys/resource.h>
#include <ctype.h>
#include <time.h>
#define VERSION "1.0.5"
#define CTIME_R_2 1
char *lock_file = NULL;
int lock_file_fd = 0;
int kill_sig = 0;
int daemon_no_close = 0;
int child_pid = -1;
int daemon_closed = 0;
int uid = 0;
int gid = 0;
char *log_file = NULL;
size_t log_rotate_size = 0;
unsigned open_file_limited = 65536;
int log_pipe[2];
void my_msleep(int msec)
{
        struct timeval tv;
        tv.tv_sec = msec / 1000;
        tv.tv_usec = (msec % 1000) * 1000;
        select(1, NULL, NULL, NULL, &tv);
}
int name2uid(const char *name, int *uid, int *gid) {
	if (name == NULL) {
		return 1;
	}
	if (name[0] == '#') {
		*uid = atoi(name + 1);
		return 0;
	}
	if (isdigit((unsigned char) *name)) {
		*uid = atoi(name);
		return 0;
	}
	char buf[512];
	struct passwd pwd;
	struct passwd *result;
	if (getpwnam_r(name, &pwd, buf, sizeof(buf), &result) != 0) {
		return 1;
	}
	if (result == NULL) {
		return 1;
	}
	*uid = result->pw_uid;
	*gid = result->pw_gid;
	return 0; 
}

int name2gid(const char *name, int *gid) {
	if (name == NULL) {
		return 1;
	}
	if (name[0] == '#') {
		*gid = atoi(name + 1);
		return 0;
	}
	if (isdigit((unsigned char) *name)) {
                *gid = atoi(name);
                return 0;
        }
	char buf[512];
	struct group grp;
	struct group *result;
	if (getgrnam_r(name, &grp, buf, sizeof(buf), &result) != 0) {
		return 1;
	}
	if (result == NULL) {
		return 1;
	}
	*gid = result->gr_gid;
	return 0;
}
void init_resource_limit()
{
	struct rlimit rlim;
	if (open_file_limited < 1024) {
		open_file_limited = 1024;
	}
	if (0==getrlimit(RLIMIT_NOFILE,&rlim)) {
		if (rlim.rlim_max < open_file_limited) {
			rlim.rlim_cur = open_file_limited;
			rlim.rlim_max = open_file_limited;
			int ret = setrlimit(RLIMIT_NOFILE,&rlim);
			if (ret!=0) {
				syslog(LOG_NOTICE,"set open file limit error [%d]\n",errno);
			}
		}
	}
	if (0==getrlimit(RLIMIT_NOFILE,&rlim)) {
		syslog(LOG_NOTICE,"max open file limit [cur:%d,max:%d]\n",rlim.rlim_cur,rlim.rlim_max);
	} else {
		syslog(LOG_NOTICE,"get max open file limit error [%d]\n",errno);
	}
}
size_t get_size(const char *size) {
	if (*size=='\0') {
		return 0;
	}
	size_t s = (size_t)atoi(size);
	char last_char = size[strlen(size)-1];
	switch (last_char) {
	case 'k':
	case 'K':
		return s<<10;
	case 'm':
	case 'M':
		return s<<20;
	case 'g':
	case 'G':
		return s<<30;
	default:
		return s;
	}
}
int parse_args(int argc,char **argv) {
	int c;
	struct option long_options[] = {
			{ "reboot", 0, 0, 'r' },
			{ "version", 0, 0, 'v' },
			{ "help", 0, 0,'h' },
			{ "quit", 0, 0,'q' },
			{"lock",required_argument,0,'l'},
			{ 0, 0, 0, 0 }
	};
	int opt_index = 0;
	while ((c = getopt_long(argc, argv, "o:rvhnqf:u:l:?", long_options, &opt_index)) != -1) {
			switch (c) {
			case 'u':
				if (getuid()!=0) {
					fprintf(stderr,"warning not root user\n");
				}
				char *user = optarg;
				char *group = strchr(optarg,':');
				if (group) {
					*group = '\0';
					group++;
				}
				if (name2uid(user,&uid,&gid)!=0) {
					fprintf(stderr,"Error, cann't find user [%s]\n",user);
				}
				if (group && *group) {
					if (name2gid(group,&gid)!=0) {
						fprintf(stderr,"Error,cann't find group [%s]\n",group);
					}
				}
				break;
			case 'o':
				log_file = strdup(optarg);
				char *p = strchr(log_file,':');
				if (p!=NULL) {
					*p = '\0';
					log_rotate_size = get_size(p+1);
				}
				break;
			case 'l':
				lock_file = strdup(optarg);
				break;
			case 'f':
				open_file_limited = (unsigned)atoi(optarg);
				break;
			case 'r':
				kill_sig = SIGUSR1;
				break;
			case 'q':
				kill_sig = SIGTERM;
				break;
			case 'h':
				return -1;
			case 'n':
				daemon_no_close = 1;
				break;
			}
	}
	int ret = optind;
	optind = 0;
	return ret;
}
int save_pid(pid_t pid) {
	 char buf[16];
	 memset(buf, 0, sizeof(buf));
	int len = snprintf(buf, sizeof(buf) - 2, "%d",pid);
	write(lock_file_fd, buf, len);
	return 0;
}
int check_running(pid_t *pid) {
	lock_file_fd = open(lock_file, O_RDWR);
	if  (lock_file_fd<0)  {
		//open lock file failed program not running
		return -2;
	}
	 struct flock lock;
	memset(&lock, 0, sizeof(lock));
	lock.l_type = F_WRLCK;
	if (fcntl(lock_file_fd, F_SETLK, &lock) != -1) {
		//lock success program not running
			close(lock_file_fd);
			lock_file_fd = -1;
			return -1;
	}
	//lock failed program is running
	char buf[16];
	memset(buf, 0, sizeof(buf));
	read(lock_file_fd, buf, sizeof(buf) - 1);
	close(lock_file_fd);
	lock_file_fd = -1;
	*pid = atoi(buf);
	return 0;
}
int file_lock() {
	lock_file_fd = open(lock_file, O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (lock_file_fd<0) {
		return -1;
	}
	 struct flock lock;
	memset(&lock, 0, sizeof(lock));
	lock.l_type = F_WRLCK;
	if (fcntl(lock_file_fd, F_SETLKW, &lock) == -1) {
		   fprintf(stderr, "lock failed errno=%d.\n", errno);
			return -1;
	}
	return save_pid(getpid());
}
void usage() {
	printf("daemon version [%s]\nusage: daemon [-l lockfile] [-u <user|#uid>[:group]] [-o log_file[:rotate_size]] [-f #max_open_files] [-q] [-r] [-n] [-- cmd arg]\n",VERSION);
}
int kill_signal(int sig) {
	pid_t pid;
	int i;
	int ret = check_running(&pid);
	if (ret!=0) {
		fprintf(stderr,"program is not running\n");
		return ret;
	}
	ret = kill(pid,sig);
	if (ret!=0) {
		fprintf(stderr,"kill sig to pid=[%d] error\n",pid);
		return ret;
	}
	if (sig!=SIGTERM) {
		return 0;
	}
	//SIGTERM must wait
	for (i=0;i<100;i++) {
		ret = check_running(&pid);
		if (ret!=0) {
			return 0;
		}
		my_msleep(100);
	}
	fprintf(stderr,"wait program exit timeout\n");
	return 1;
}
void sigcatch(int sig) {
	//printf("recv sig[%d]\n",sig);
        signal(sig, sigcatch);
        switch(sig) {
		 case SIGTERM:
		 case SIGINT:
		 case SIGQUIT:
			daemon_closed = 1;
			if (child_pid>0) {
				kill(child_pid,sig);
			} else {
				exit(0);
			}
			break;
		 case SIGUSR1:
			if (child_pid>0) {
				kill(child_pid,SIGQUIT);
			}
			break;
		case SIGCHLD:
			if (log_pipe[0]>=0) {
				close(log_pipe[0]);
			}
			break;
        }
}
void init_signal()
{
#ifndef _WIN32
        umask(0022);
        signal(SIGPIPE, SIG_IGN);
        signal(SIGHUP, sigcatch);
        signal(SIGINT, sigcatch);
        signal(SIGTERM, sigcatch);
        signal(SIGUSR1, sigcatch);
        signal(SIGQUIT, sigcatch);
        signal(SIGCHLD, sigcatch);
#endif
}
size_t current_log_size = 0;
int log_pid = 0;
void rotate_log(int level) {
	if (log_file) {
		int len = strlen(log_file);
		int new_len = len + 8;
		char *old_file = (char *)malloc(new_len);
		memset(old_file,0,new_len);
		if (level>0) {
			snprintf(old_file,len+8,"%s.%d",log_file,level);
		} else {
			snprintf(old_file,len+8,"%s",log_file);
		}
		if (level >= 10) {
			unlink(old_file);
			free(old_file);
			return;
		}
		struct stat buf;
		if (stat(old_file, &buf) == 0) {
			rotate_log(level + 1);
		}
		char *new_file = (char *)malloc(new_len);
		memset(new_file,0,new_len);
		snprintf(new_file,len+8,"%s.%d",log_file,level+1);
		rename(old_file, new_file);
		free(old_file);
		free(new_file);
	}
}
int open_log_file() {
	if (log_file) {
		int flag = O_WRONLY|O_APPEND|O_CREAT;
#ifdef O_CLOEXEC
		flag |= O_CLOEXEC;
#endif
		int fd = open(log_file,flag,0600);
		if (fd>=0) {
			current_log_size = 0;
			struct stat buf;
			if (fstat(fd,&buf)==0) {
				current_log_size = buf.st_size;
			}
		}
		return fd;
	}
	return -1;
}
void write_log_time()
{
	if (log_rotate_size>0 && current_log_size>log_rotate_size) {
		close(log_pid);
		rotate_log(0);
		log_pid = open_log_file();
	}
	time_t ltime;
	time(&ltime);
	char tm[30];
#ifdef CTIME_R_2
	ctime_r(&ltime, tm);
#else
	ctime_r(&ltime, tm, sizeof(tm));
#endif
	tm[19] = 0;
	write(log_pid,tm,19);
	write(log_pid,"|",1);
	current_log_size += 20;
}
void write_log_msg(char *msg,int len) {
	len = write(log_pid,msg,len);
	if (len>0) {
		current_log_size+=len;
	}
}
int write_log(char *msg,int len) {
	if (log_pid<0) {
		return write(1,msg,len);
	}
	while (len>0) {
		char *line = memchr(msg,'\n',len);
		if (line!=NULL) {
			int line_len = line - msg + 1;
			write_log_msg(msg,line_len);
			write_log_time();
			msg += line_len;
			len -= line_len;
			continue;			
		} else {
			write_log_msg(msg,len);
			break;
		}
	}
}
int wait_child() {
	int status;
	int exit_pid = waitpid(-1,&status,WNOHANG);
	if (exit_pid<=0) {
		my_msleep(100);
	}
	printf("child program exsit with status=%d\n",status);
	if (status==0) {
		exit(0);
	}

}
void my_fork()
{
	log_pid = open_log_file();
	child_pid = -1;
	for (;;) {
		if (child_pid==-1) {
			if (pipe(log_pipe)!=0) {
				fprintf(stderr,"cann't call pipe\n");
				my_msleep(1000);
				continue;
			}
			child_pid = fork();
			if (child_pid==0) {
				close(log_pipe[0]);
				dup2(log_pipe[1],1);
				dup2(log_pipe[1],2);
				return;
			}
			close(log_pipe[1]);
			if (child_pid<0) {
				my_msleep(1000);
				close(log_pipe[0]);
				continue;
			}
			for (;;) {
				char buf[512];
				int len = read(log_pipe[0],buf,sizeof(buf));
				if (len<=0) {
					break;
				}
				write_log(buf,len);
			}
			close(log_pipe[0]);
		}
		int status;
		int exit_pid = waitpid(-1,&status,WNOHANG);
		if (exit_pid<=0) {
			my_msleep(1000);
			continue;
		}
		printf("child program exsit with status=%d\n",status);
		if (status==0) {
			exit(0);
		}
		if (daemon_closed || lock_file==NULL) {
			exit(status);
		}
		child_pid = -1;
	}
}

int main(int argc,char **argv) {
	int ret = parse_args(argc,argv);
	if (ret<=0) {
		usage();
		return 1;
	}
	if (kill_sig>0) {
		return kill_signal(kill_sig);
	}
	pid_t pid = 0;
	if (lock_file) {
		int run_result = check_running(&pid);
		if (run_result==0) {
			fprintf(stderr,"process pid=[%d] is under running\n",pid);
			return 1;
		}
	}
	init_resource_limit();
	if (daemon_no_close==0) {
		daemon(1,daemon_no_close);
	}
	if (lock_file && file_lock()!=0) {
		fprintf(stderr,"lock file [%s] failed [%d %s]\n",lock_file,errno,strerror(errno));
		return 1;
	}
	if (argc<=0) {
		fprintf(stderr,"cmd must be set\n");
		return 1;
	}
	init_signal();
	my_fork();
#ifndef O_CLOEXEC
	close(log_pid);
#endif
	if (uid>0||gid>0) {
		setgid(gid);
		setuid(uid);
	}
	argc -= ret;
	argv+= ret;
	execv(argv[0],argv);
	fprintf(stderr,"cann't exec cmd [%d %s]\n",errno,strerror(errno));
	return 0;
}
